package at.jku.ssw.fortress.ui;

import java.util.List;

import com.sun.fortress.nodes.AbsFnDecl;
import com.sun.fortress.nodes.AbsObjectDecl;
import com.sun.fortress.nodes.AbsTraitDecl;
import com.sun.fortress.nodes.AbsVarDecl;
import com.sun.fortress.nodes.ArrowType;
import com.sun.fortress.nodes.BaseType;
import com.sun.fortress.nodes.FnAbsDeclOrDecl;
import com.sun.fortress.nodes.FnDecl;
import com.sun.fortress.nodes.LValue;
import com.sun.fortress.nodes.LValueBind;
import com.sun.fortress.nodes.NatParam;
import com.sun.fortress.nodes.Node;
import com.sun.fortress.nodes.NodeAbstractVisitor_void;
import com.sun.fortress.nodes.NormalParam;
import com.sun.fortress.nodes.ObjectAbsDeclOrDecl;
import com.sun.fortress.nodes.ObjectDecl;
import com.sun.fortress.nodes.Param;
import com.sun.fortress.nodes.StaticParam;
import com.sun.fortress.nodes.TemplateGapParam;
import com.sun.fortress.nodes.TraitAbsDeclOrDecl;
import com.sun.fortress.nodes.TraitDecl;
import com.sun.fortress.nodes.TupleType;
import com.sun.fortress.nodes.Type;
import com.sun.fortress.nodes.TypeArg;
import com.sun.fortress.nodes.VarAbsDeclOrDecl;
import com.sun.fortress.nodes.VarDecl;
import com.sun.fortress.nodes.VarargsParam;
import com.sun.fortress.nodes.VoidType;
import com.sun.fortress.nodes._EllipsesParam;
import com.sun.fortress.nodes._EllipsesType;

import edu.rice.cs.plt.tuple.Option;

public class SignatureVisitor extends NodeAbstractVisitor_void{

	public static String signature(Node n) {
		SignatureVisitor v = new SignatureVisitor();
		n.accept(v);
		return v.b.toString();
	}

	private StringBuilder b = new StringBuilder();

	private void app(String s) {
		b.append(s);
	}

	@Override
	public void forTraitDecl(TraitDecl x) {
	    visitTraitAbsDeclOrDecl(x);
	}

	@Override
	public void forAbsTraitDecl(AbsTraitDecl x) {
	    visitTraitAbsDeclOrDecl(x);
	}
	
	private void visitTraitAbsDeclOrDecl(TraitAbsDeclOrDecl x) {
		app(x.getName().getText());
		for(StaticParam sp : x.getStaticParams())
    		forStaticParam(sp);
	}

	@Override
	public void forObjectDecl(ObjectDecl x) {
		visitObjectAbsDeclOrDecl(x);
	}

	@Override
	public void forAbsObjectDecl(AbsObjectDecl x) {
		visitObjectAbsDeclOrDecl(x);
	}
	
	private void visitObjectAbsDeclOrDecl(ObjectAbsDeclOrDecl x) {
		app(x.getName().getText());
        for(StaticParam sp : x.getStaticParams())
            forStaticParam(sp);
	}

	@Override
	public void forVarDecl(VarDecl x) {
		visitVarAbsDeclOrDecl(x);
	}

	@Override
	public void forAbsVarDecl(AbsVarDecl x) {
		visitVarAbsDeclOrDecl(x);
	}
	
	private void visitVarAbsDeclOrDecl(VarAbsDeclOrDecl x) {
		for (LValue v : x.getLhs()) {
			if (v instanceof LValueBind) {
				LValueBind vb = (LValueBind) v;
				app(vb.getName().getText());
				app(":");
				if (vb.getType().isSome()) {
					visit(vb.getType().unwrap());
				} else {
					b.append("??");
				}
			}
		}
	}

	@Override
	public void forFnDecl(FnDecl x) {
		visitAbsFnAbsDeclOrDecl(x);
	}
	
	@Override
	public void forAbsFnDecl(AbsFnDecl x) {
		visitAbsFnAbsDeclOrDecl(x);
	}
	
	private void visitAbsFnAbsDeclOrDecl(FnAbsDeclOrDecl x) {
		app(x.stringName());
		for(StaticParam sp : x.getStaticParams())
		    forStaticParam(sp);
		app("(");
		boolean first = true;
		for (Param p : x.getParams()) {
			if (!first)
				app(",");
			first = false;
			visit(p);
		}
		app("):");
		Option<Type> rt = x.getReturnType();
		if (rt.isSome()) {
			visit(rt.unwrap());
		} else {
			b.append("??");
		}
	}
	
	@Override
	public void forTypeArg(TypeArg x) {
		visit(x.getType());
	}

	@Override
	public void forBaseType(BaseType x) {
	    if(x.isParenthesized()){
	        app("(" + x.stringName() + ")");
	    }
		app("" + x.stringName());
	}

	@Override
	public void forTupleType(TupleType x) {
		app("("); 
		visitList(x.getElements(), " ");
		app(")");
	}


	//TODO: add these two
//	@Override
//	public void forIdType(IdType x) {
//	    b.append(x.getName().name());
//	}
//
//	
//	@Override
//	public void forParamType(ParamType x) {
//		visit(x.getGeneric());
//		app("[\\");
//		visitList(x.getArgs(), ",");
//		app("\\]");
//	}

	@Override
	public void forVoidType(VoidType x) {
		app("()");
	}

	public void for_EllipsesType(_EllipsesType x) {
		visit(x.getRepeatedNode());
		app("...");
	}

	@Override
	public void forArrowType(ArrowType x) {
		app("(");
		visit(x.getDomain());
		app(")");
		app("->");
		visit(x.getRange());
	}

	@Override
    public void forNormalParam(NormalParam x) {
	    
	    app(x.getName().getText());
        app(":");
        if(x.getType().isSome())
            visit(x.getType().unwrap());   
        else
            app("??");
	}

	@Override
    public void for_EllipsesParam(_EllipsesParam x) {
       app(x.getName().getText());
       app(":");
       visit(x.getRepeatedNode());
       app("...");
    }

    @Override
    public void forVarargsParam(VarargsParam x) {
        app(x.getName().getText());
        app(":");
        visit(x.getType());
        app("...");
    }

    @Override
	public void forNatParam(NatParam x) {
		app("nat ");
		app(x.getName().getText());
	}


	protected void visitList(List<? extends Type> refs, String seperator) {
		boolean first = true;
		for (Type n : refs) {
			if (!first)
				app(seperator);
			first = false;
			visit(n);
		}
	}

	private void visit(Node n) {
		n.accept(this);
	}

}
